package xj.graph2d.tools;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.Observer;

import xj.graph2d.Command;
import xj.graph2d.Document;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.DrawShapeFactory;
import xj.graph2d.GraphDocument;
import xj.graph2d.GraphEdgeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.GraphView;
import xj.graph2d.OpenShape;
import xj.graph2d.SelfLoop;
import xj.graph2d.command.AddEdgeCommand;
import xj.graph2d.command.AddShapeCommand;
import xj.graph2d.graph.GraphNode;
import xj.util.gui.Point2DFloat;

public class TwoEndsDrawTool extends SimpleDrawTool {

  public TwoEndsDrawTool(DrawShape shape) {
    this(shape, DrawShapeConstants.USE_SYSTEM_DRAW_ATTR, false, null);
  }

  public TwoEndsDrawTool(DrawShape shape, int cloneDrawAttrMode) {
    this(shape, cloneDrawAttrMode, false, null);
  }

  public TwoEndsDrawTool(DrawShape shape, int cloneDrawAttrMode, String shapeKey) {
    this(shape, cloneDrawAttrMode, false, shapeKey);
  }

  public TwoEndsDrawTool(DrawShape shape, int cloneDrawAttrMode, 
			 boolean reverseEnds, String shapeKey) {
    super(cloneDrawAttrMode);
    this.shape = shape;
    this.reverseEnds = reverseEnds;
    this.shapeKey = shapeKey;
  }

  public void setReverseEnds(boolean reverseEnds) { 
    this.reverseEnds = reverseEnds;
  }

  public boolean isReverseEnds() { 
    return reverseEnds;
  }

  @Override
  public void mouseLButtonPressed(Observer observer, Point p, int modifiers) {
    mouseButtonDown = true;
    start(observer);
    src0 = null;
    loop = null;
    if (shape != null && view != null) {
      Graphics2D g2 = canvas.getGraphics2D();
      canvas.setTransform(g2);
      DrawShape selected = doc.getSelectedShape();
      doc.setSelectedShape(null);
      canvas.repaintShape(selected);
      canvas.setEditingMode(true);
      prev = view.physicalToVirtual(p);
      float x = (float) prev.getX();
      float y = (float) prev.getY();
      start = new Point2DFloat(x, y);
      if (cloneDrawAttrMode == USE_SYSTEM_DRAW_ATTR || 
	  cloneDrawAttrMode == CLONE_SYSTEM_DRAW_ATTR) {
	shape.setDrawAttr(view.getDrawAttr());
      }
      checkSelfLoop();

      adjustShape(shape, x, y, x, y);
      canvas.clearRepaintShapes();
      canvas.addRepaintShape(shape);
      canvas.repaint();
      g2.dispose();
    }
  }

  @Override
  public void mouseDragged(Observer observer, Point p, int modifiers) {
    if (mouseButtonDown && 
	shape != null && 
	start != null && 
	view != null) {

      displayLocation(p);
      if (shapeKey != null) {
	view.displayHelpMessage(shapeKey + MSGKEY_2);
      }

      Graphics2D g2 = canvas.getGraphics2D();
      Point2D cur = view.physicalToVirtual(p);
      float x0, y0, x1, y1;
      if (reverseEnds) { 
	x1 = (float) start.getX();
	y1 = (float) start.getY();
	x0 = (float) cur.getX();
	y0 = (float) cur.getY();
      } else { 
	x0 = (float) start.getX();
	y0 = (float) start.getY();
	x1 = (float) cur.getX();
	y1 = (float) cur.getY();
      }
      if (src0 != null && loop != null) {
	loop.adjustDirection(src0.getBounds(), x0, y0, x1, y1);
      }
      adjustShape(shape, x0, y0, x1, y1);
      canvas.repaint();
      prev = cur;
      g2.dispose();
    }
  }

  @Override
  public void mouseLButtonReleased(Observer observer, Point p, int modifiers) {
    if (mouseButtonDown && 
	shape != null && view != null) {
      canvas.setEditingMode(false);
      Point2D cur = view.physicalToVirtual(p);

      if (reverseEnds) { 
	Point2D t = cur;
	cur = start;
	start = t; 
      }

      float x0 = (float) start.getX();
      float y0 = (float) start.getY();
      float x1 = (float) cur.getX();
      float y1 = (float) cur.getY();
      if (!isZeroSizedShape(x0, y0, x1, y1)) {
	DrawShape newShape = DrawShapeFactory.cloneDrawShape(shape, view,
							     cloneDrawAttrMode);
	adjustShape(newShape, x0, y0, x1, y1);

	if (false) {
	  System.out.println("TwoEndsDrawTool.mouseLButtonReleased() @2");

	  Object m = newShape.getModel();
	  if (m instanceof xj.model.uml.UMLAssociation) {
	    xj.model.uml.UMLAssociation a = (xj.model.uml.UMLAssociation) m;
	    System.out.println("   model: UMLAssociation navRight=" + a.getNavigabilityToRight() + 
			       "  navLeft=" + a.getNavigabilityToLeft());
	  }
	}

	Command cmd = null;
	if (graphMode && 
	    shape instanceof GraphEdgeBase && 
	    doc instanceof GraphDocument && 
	    doc.getDocumentType() != Document.DocumentType.Generic_Diagram &&
	    view instanceof GraphView) {
	  GraphEdgeBase newEdge = (GraphEdgeBase) newShape;
	  GraphDocument graph = (GraphDocument) doc;
	  GraphView gview = (GraphView) view;

	  cmd = new AddEdgeCommand(gview, graph, newEdge);
	  doc.executeCommand(cmd);

	  GraphNodeBase src = graph.searchNode(start);
	  GraphNodeBase des = graph.searchNode(cur);
	  if (src != null) {
	    graph.connectSource(newEdge, src);
	  }
	  if (des != null) {
	    graph.connectDestination(newEdge, des);
	  }

	  newEdge.adjustControlPoints();
	  newEdge.adjustEnds();
	}

	if (false) {
	  Object m = newShape.getModel();
	  if (m instanceof xj.model.uml.UMLAssociation) {
	    xj.model.uml.UMLAssociation a = (xj.model.uml.UMLAssociation) m;
	    System.out.println("   model: UMLAssociation navRight=" + 
			       a.getNavigabilityToRight() + 
			       "  navLeft=" + a.getNavigabilityToLeft());
	  }
	}

	if (cmd == null) {
	  cmd = new AddShapeCommand(view, doc, newShape);
	  doc.executeCommand(cmd);
	}

	if (false) {
	  System.out.println("TwoEndsDrawTool.mouseLButtonReleased() @4");
	  Object m = newShape.getModel();
	  if (m instanceof xj.model.uml.UMLAssociation) {
	    xj.model.uml.UMLAssociation a = (xj.model.uml.UMLAssociation) m;
	    System.out.println("   model: UMLAssociation navRight="
			       + a.getNavigabilityToRight() + "  navLeft="
			       + a.getNavigabilityToLeft());
	  }
	}

	if (switchTool != null) {
	  switchTool();
	  switchTool.mouseLButtonPressed(observer, p, 0);
	  switchTool.mouseLButtonReleased(observer, p, 0);
	}
      }

      canvas.clearRepaintShapes();
      canvas.repaint();
    }
    mouseButtonDown = false;
  }

  protected void adjustShape(DrawShape shape, 
			     float x1, float y1, 
			     float x2, float y2) {
    if (shape != null) {
      shape.setEnds(x1, y1, x2, y2);

      if (shape instanceof SelfLoop) {
	SelfLoop sloop = (SelfLoop) shape;
	if (src0 != null) { 
	  sloop.adjustDirection(src0.getBounds(), x1, y1, x2, y2);
	}
	sloop.adjustSelfLoop();
      }
    }
  }

  private boolean isZeroSizedShape(float x0, float y0, float x1, float y1) {
    if (shape != null) {  
      if (shape instanceof OpenShape) {
	return (Math.abs(x1 - x0) < 1f && Math.abs(y1 - y0) < 1f);
      } else {
	return (Math.abs(x1 - x0) < 1f || Math.abs(y1 - y0) < 1f);
      }
    }
    return true;
  }

  @Override
  public void mouseExited(Observer observer, Point p, int modifiers) {
    if (view != null && shape != null) {
      canvas.clearRepaintShapes();
      canvas.repaint();
    }
  }

  @Override
  public void mouseRButtonPressed(Observer observer, Point p, int modifiers) {
    start(observer);
    if (!mouseButtonDown && view != null) {
      Graphics2D g2 = canvas.getGraphics2D();

      DrawShape prevSelectedShape = doc.getSelectedShape();
      if (prevSelectedShape != null) {
	doc.setSelectedShape(null);
	canvas.repaintShape(prevSelectedShape, true);
      }

    }

  }

  @Override
  public void popupTriggered(Observer observer, Point p, int modifiers) {
    //System.out.println("OnePointShapeTool.popupTriggered()");
    if (view != null && shape != null) {
      canvas.clearRepaintShapes();
      canvas.repaint();
    }
    if (switchTool != null) {
      switchTool();
    }
  }

  @Override
  public void mouseMoved(Observer observer, Point p, int modifiers) {
    if (view != null) {
      displayLocation(p);
      if (shapeKey != null) {
	view.displayHelpMessage(shapeKey + MSGKEY_1);
      }
    }
  }

  protected void checkSelfLoop() {
    if (graphMode && 
	shape instanceof GraphEdgeBase && 
	doc instanceof GraphDocument) {
      GraphDocument graph = (GraphDocument) doc;
      GraphNode src = graph.searchNode(start);
      if (src instanceof GraphNodeBase) {
	src0 = (GraphNodeBase) src;
      }
      if (shape instanceof SelfLoop) {
	loop = (SelfLoop) shape;
      }
    }
  }

  protected Point2D start;

  protected DrawShape shape;

  protected String shapeKey;

  protected GraphNodeBase src0 = null;

  protected SelfLoop loop = null;

  protected boolean reverseEnds = false;

  protected static final String MSGKEY_1 = "Tool.message.1";

  protected static final String MSGKEY_2 = "Tool.message.2";

}
